// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Copyright (C) 2016-2018, LomboTech Co.Ltd.
 * Authors:
 *	lomboswer <lomboswer@lombotech.com>
 *
 * This program is free software; you can redistribute  it and/or modify it
 * under  the terms of  the GNU General  Public License as published by the
 * Free Software Foundation;  either version 2 of the  License, or (at your
 * option) any later version.
 */

#include <linux/backlight.h>
#include <linux/module.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/regulator/consumer.h>
#include <linux/iio/iio.h>

#include <drm/drmP.h>
#include <drm/drm_mipi_dsi.h>
#include <drm/drm_panel.h>

#define OTM_WIDTH	(720)
#define OTM_HEIGHT	(1280)
#define OTM_HBP		(40)
#define OTM_HFP		(40)
#define OTM_HSW		(20)
#define OTM_HACT	(OTM_WIDTH)
#define OTM_VBP		(7)
#define OTM_VFP		(20)
#define OTM_VSW		(6)
#define OTM_VACT	(OTM_HEIGHT)
#define OTM_VT		(OTM_VBP + OTM_VFP + OTM_VSW + OTM_VACT)
#define OTM_DCLK	(59400000)
#define DCS_MDELAY_FLAG (0)

struct panel_otm1289a_desc {
	const struct drm_display_mode *modes;
	unsigned int num_modes;
	const struct display_timing *timings;
	unsigned int num_timings;

	unsigned int bpc;

	struct {
		unsigned int width;
		unsigned int height;
	} size;

	/**
	 * @prepare: the time (in milliseconds) that it takes for the panel to
	 *           become ready and start receiving video data
	 * @enable: the time (in milliseconds) that it takes for the panel to
	 *          display the first valid frame after starting to receive
	 *          video data
	 * @disable: the time (in milliseconds) that it takes for the panel to
	 *           turn the display off (no content is visible)
	 * @unprepare: the time (in milliseconds) that it takes for the panel
	 *             to power itself down completely
	 */
	struct {
		unsigned int prepare;
		unsigned int enable;
		unsigned int disable;
		unsigned int unprepare;
	} delay;

	unsigned long flag;
	unsigned int lanes;
	enum mipi_dsi_pixel_format format;
};

struct panel_otm1289a {
	struct drm_panel base;
	bool prepared;
	bool enabled;

	struct device *dev;
	const struct panel_otm1289a_desc *desc;

	struct backlight_device *backlight;
	struct regulator *supply;
	int reset_gpio;
	int power_gpio;
	int sel_gpio;
};

static u8 otm1289a_init_code[] = {
	2, 0x00, 0x00,
	4, 0xff, 0x12, 0x89, 0x01,
	2, 0x00, 0x80,
	3, 0xff, 0x12, 0x89,
	2, 0x00, 0x90,
	2, 0xff, 0xb0,
	2, 0x00, 0x80,
	9, 0xc0, 0x4a, 0x00, 0x10, 0x10, 0x96, 0x01, 0x68, 0x40,
	2, 0x00, 0x90,
	4, 0xc0, 0x3b, 0x01, 0x09,
	2, 0x00, 0x8c,
	2, 0xc0, 0x00,
	2, 0x00, 0x80,
	2, 0xc1, 0x33,
	2, 0x00, 0x85,
	4, 0xc5, 0x0a, 0x0a, 0x46,
	2, 0x00, 0x00,
	3, 0xd8, 0x27, 0x27,
	2, 0x00, 0x01,
	2, 0xd9, 0x7d,
	2, 0x00, 0x84,
	2, 0xC4, 0x02,
	2, 0x00, 0x93,
	2, 0xC4, 0x04,
	2, 0x00, 0x96,
	2, 0xF5, 0xE7,
	2, 0x00, 0xA0,
	2, 0xF5, 0x4A,
	2, 0x00, 0x8a,
	2, 0xc0, 0x11,
	2, 0x00, 0x83,
	2, 0xF5, 0x81,
	2, 0x00, 0x90,
	3, 0xc4, 0x96, 0x05,
	2, 0x00, 0x80,
	16, 0xcb, 0x14, 0x14, 0x14, 0x14, 0x14, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	2, 0x00, 0x90,
	8, 0xcb, 0xFC, 0xFC, 0xFC, 0x00, 0x14, 0x14, 0x14,
	2, 0x00, 0x80,
	15, 0xcc, 0x02, 0x0A, 0x0C, 0x0E, 0x10, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	2, 0x00, 0x90,
	16, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x1E, 0x1D, 0x06, 0x01,
		0x09, 0x0B, 0x0d, 0x0F, 0x00, 0x00, 0x00,
	2, 0x00, 0xa0,
	14, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x1E, 0x1D, 0x05,
	2, 0x00, 0xb0,
	15, 0xcc, 0x05, 0x0F, 0x0D, 0x0B, 0x09, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	2, 0x00, 0xc0,
	16, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x1D, 0x1E, 0x01, 0x06,
		0x10, 0x0E, 0x0C, 0x0A, 0x00, 0x00, 0x00,
	2, 0x00, 0xd0,
	14, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x1D, 0x1E, 0x02,
	2, 0x00, 0x80,
	7, 0xce, 0x87, 0x03, 0x10, 0x86, 0x00, 0x00,
	2, 0x00, 0x90,
	10, 0xce, 0x34, 0xff, 0x10, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00,
	2, 0x00, 0xa0,
	16, 0xce, 0x30, 0x83, 0x88, 0x00, 0x20, 0x00, 0x82, 0x87,
		0x00, 0x81, 0x86, 0x00, 0x80, 0x85, 0x00,
	2, 0x00, 0xb0,
	16, 0xce, 0x30, 0x00, 0x84, 0x00, 0x20, 0x00, 0x01, 0x83,
		0x00, 0x02, 0x82, 0x00, 0x03, 0x81, 0x00,
	2, 0x00, 0xE0,
	9, 0xCE, 0x0A, 0x04, 0xFC, 0x00, 0x00, 0x0A, 0x04, 0xFC,
	2, 0x00, 0xF0,
	7, 0xCE, 0x01, 0x20, 0x01, 0x01, 0x00, 0x00,
	2, 0x00, 0x00,
	17, 0xE1, 0x00, 0x16, 0x26, 0x37, 0x47, 0x65, 0x64, 0x7d,
		0x76, 0x62, 0x69, 0x50, 0x38, 0x21, 0x13, 0x00,
	2, 0x00, 0x00,
	17, 0xE2, 0x00, 0x16, 0x26, 0x37, 0x47, 0x65, 0x64, 0x7d,
		0x76, 0x62, 0x69, 0x50, 0x38, 0x21, 0x13, 0x00,
	2, 0x00, 0x00,
	4, 0xff, 0xff, 0xff, 0xff,
	1,  0x11,
	DCS_MDELAY_FLAG, 200,
	1,  0x29,
	DCS_MDELAY_FLAG, 120,
};

static int panel_otm1289a_dsi_dcs_write_array(
	struct mipi_dsi_device *dsi, const u8 *ini_code, size_t len)
{
	u32 i = 0;

	while (i < len) {
		if (ini_code[i] == DCS_MDELAY_FLAG) {
			mdelay(ini_code[i + 1]);
			i += 2;
		} else {
			DRM_DEBUG_KMS("%x, %x\n", ini_code[i + 1],
					ini_code[i + 2]);
			mipi_dsi_dcs_write_buffer(dsi,
				(const void *)&ini_code[i + 1], ini_code[i]);
			i += ini_code[i] + 1;
		}
	}
	return 0;
}

static inline struct panel_otm1289a *to_panel_otm1289a(struct drm_panel *panel)
{
	return container_of(panel, struct panel_otm1289a, base);
}

static int panel_otm1289a_disable(struct drm_panel *panel)
{
	struct panel_otm1289a *p = to_panel_otm1289a(panel);

	DRM_DEBUG_KMS("[%d] p->enabled:%d\n", __LINE__, p->enabled);

	if (!p->enabled)
		return 0;

	if (p->backlight) {
		p->backlight->props.power = FB_BLANK_POWERDOWN;
		p->backlight->props.state |= BL_CORE_FBBLANK;
		backlight_update_status(p->backlight);
	}

	if (p->desc && p->desc->delay.disable)
		msleep(p->desc->delay.disable);

	p->enabled = false;

	return 0;
}

static int panel_otm1289a_unprepare(struct drm_panel *panel)
{
	struct panel_otm1289a *p = to_panel_otm1289a(panel);

	DRM_DEBUG_KMS("[%d] p->prepared:%d\n", __LINE__, p->prepared);

	if (!p->prepared)
		return 0;

	lombo_write_ainx_output(3, 0);

	if (p->power_gpio)
		gpio_direction_output(p->power_gpio, 0);

	if (p->supply)
		regulator_disable(p->supply);

	if (p->desc && p->desc->delay.unprepare)
		msleep(p->desc->delay.unprepare);

	p->prepared = false;

	return 0;
}

static int panel_otm1289a_prepare(struct drm_panel *panel)
{
	struct panel_otm1289a *p = to_panel_otm1289a(panel);
	struct mipi_dsi_device *dsi = to_mipi_dsi_device(panel->dev);
	int err;

	DRM_DEBUG_KMS("[%d] p->prepared:%d\n", __LINE__, p->prepared);

	if (p->prepared)
		return 0;
	p->prepared = true;

	if (p->supply) {
		err = regulator_enable(p->supply);
		if (err < 0) {
			DRM_ERROR("failed to enable supply: %d\n", err);
			return err;
		}
	}

	if (p->power_gpio) {
		gpio_direction_output(p->power_gpio, 1);
		msleep(100);
	}

	if (p->sel_gpio) {
		gpio_direction_output(p->sel_gpio, 0);
		msleep(50);
	}

	lombo_write_ainx_output(3, 1);
	mdelay(100);
	lombo_write_ainx_output(3, 0);
	mdelay(100);
	lombo_write_ainx_output(3, 1);
	mdelay(100);

	if (p->desc && p->desc->delay.prepare)
		msleep(p->desc->delay.prepare);

	panel_otm1289a_dsi_dcs_write_array(dsi, otm1289a_init_code,
			ARRAY_SIZE(otm1289a_init_code));

	return 0;
}

static int panel_otm1289a_enable(struct drm_panel *panel)
{
	struct panel_otm1289a *p = to_panel_otm1289a(panel);

	DRM_DEBUG_KMS("[%d] p->enabled:%d\n", __LINE__, p->enabled);

	if (p->enabled)
		return 0;
	p->enabled = true;

	if (p->desc && p->desc->delay.enable)
		msleep(p->desc->delay.enable);

	if (p->backlight) {
		p->backlight->props.state &= ~BL_CORE_FBBLANK;
		p->backlight->props.power = FB_BLANK_UNBLANK;
		backlight_update_status(p->backlight);
	}

	return 0;
}

static int panel_otm1289a_get_fixed_modes(
	struct panel_otm1289a *panel)
{
	struct drm_connector *connector = panel->base.connector;
	struct drm_device *drm = panel->base.drm;
	struct drm_display_mode *mode;
	unsigned int i, num = 0;

	if (!panel->desc)
		return 0;

	for (i = 0; i < panel->desc->num_modes; i++) {
		const struct drm_display_mode *m = panel->desc->modes + i;

		mode = drm_mode_duplicate(drm, m);
		if (!mode) {
			DRM_ERROR("failed to add mode %ux%u@%u\n",
				m->hdisplay, m->vdisplay, m->vrefresh);
			continue;
		}
		drm_mode_set_name(mode);
		drm_mode_probed_add(connector, mode);
		num++;
	}

	connector->display_info.bpc = panel->desc->bpc;
	connector->display_info.width_mm = panel->desc->size.width;
	connector->display_info.height_mm = panel->desc->size.height;

	return num;
}

static int panel_otm1289a_get_modes(struct drm_panel *panel)
{
	struct panel_otm1289a *p = to_panel_otm1289a(panel);
	int num = 0;

	DRM_DEBUG_KMS("[%d] p->enabled:%d\n", __LINE__, p->enabled);

	/* add hard-coded panel modes */
	num += panel_otm1289a_get_fixed_modes(p);

	return num;
}

static int panel_otm1289a_get_timings(struct drm_panel *panel,
				    unsigned int num_timings,
				    struct display_timing *timings)
{
	DRM_DEBUG_KMS("%s,%d\n", __func__, __LINE__);
	return 0;
}

static const struct drm_panel_funcs panel_otm1289a_funcs = {
	.disable = panel_otm1289a_disable,
	.unprepare = panel_otm1289a_unprepare,
	.prepare = panel_otm1289a_prepare,
	.enable = panel_otm1289a_enable,
	.get_modes = panel_otm1289a_get_modes,
	.get_timings = panel_otm1289a_get_timings,
};

static int panel_otm1289a_probe(struct device *dev,
			       const struct panel_otm1289a_desc *desc)
{
	struct device_node *np_bl, *np = dev->of_node;
	struct panel_otm1289a *panel;
	enum of_gpio_flags flags;
	int err;
	struct mipi_dsi_device *dsi = to_mipi_dsi_device(dev);

	panel = devm_kzalloc(dev, sizeof(*panel), GFP_KERNEL);
	if (!panel)
		return -ENOMEM;

	panel->power_gpio =
		of_get_named_gpio_flags(np, "power_gpio", 0, &flags);
	DRM_DEBUG_KMS("power_gpio %d, flags 0x%x\n", panel->power_gpio, flags);
	if (!gpio_is_valid(panel->power_gpio)) {
		DRM_ERROR("power_gpio %d invalid\n", panel->power_gpio);
		panel->power_gpio = 0;
	} else {
		err = devm_gpio_request(dev, panel->power_gpio, "power_gpio");
		if (err < 0) {
			DRM_ERROR("power_gpio %d request failed\n",
				panel->power_gpio);
			panel->power_gpio = 0;
		}
	}

	panel->sel_gpio =
		of_get_named_gpio_flags(np, "sel_gpio", 0, &flags);
	DRM_DEBUG_KMS("sel_gpio %d, flags 0x%x\n", panel->sel_gpio, flags);
	if (!gpio_is_valid(panel->sel_gpio)) {
		DRM_ERROR("sel_gpio %d invalid\n", panel->sel_gpio);
		panel->sel_gpio = 0;
	} else {
		err = devm_gpio_request(dev, panel->sel_gpio, "sel_gpio");
		if (err < 0) {
			DRM_ERROR("sel_gpio %d request failed\n",
				panel->sel_gpio);
			panel->sel_gpio = 0;
		}
	}

	np_bl = of_parse_phandle(np, "backlight", 0);
	if (np_bl) {
		panel->backlight = of_find_backlight_by_node(np_bl);
		of_node_put(np_bl);
		if (!panel->backlight)
			DRM_ERROR("no find backlight device\n");
	}

	dsi->lanes = desc->lanes;
	dsi->format = desc->format;
	dsi->mode_flags = desc->flag;

	panel->enabled = false;
	panel->prepared = false;
	panel->desc = desc;
	panel->dev = dev;

	drm_panel_init(&panel->base);
	panel->base.dev = dev;
	panel->base.funcs = &panel_otm1289a_funcs;

	err = drm_panel_add(&panel->base);
	if (err < 0)
		goto err_out;

	mipi_dsi_attach(dsi);
	if (err < 0)
		goto err_out;

	dev_set_drvdata(dev, panel);

	DRM_DEBUG_KMS("[%d] over.\n", __LINE__);

	return 0;

err_out:
	put_device(&panel->backlight->dev);
	return err;
}

static int panel_otm1289a_remove(struct mipi_dsi_device *dsi)
{
	struct panel_otm1289a *panel = dev_get_drvdata(&dsi->dev);

	mipi_dsi_detach(dsi);
	drm_panel_detach(&panel->base);
	drm_panel_remove(&panel->base);

	panel_otm1289a_disable(&panel->base);

	if (panel->backlight)
		put_device(&panel->backlight->dev);

	return 0;
}

static void panel_otm1289a_shutdown(struct mipi_dsi_device *dsi)
{
	struct panel_otm1289a *panel = dev_get_drvdata(&dsi->dev);

	panel_otm1289a_disable(&panel->base);
}

static const struct drm_display_mode otm1289a_mode = {
	/* dclk_freq */
	.clock = OTM_DCLK / 1000,
	/* width */
	.hdisplay = OTM_HACT,
	/* hsync_start = hdisplay + hfp */
	.hsync_start = OTM_HACT + OTM_HFP,
	/* hsync_end = hdisplay + hfp + hsw */
	.hsync_end = OTM_HACT + OTM_HFP + OTM_HSW,
	/* htotal = hdisplay + hfp + hsw + hbp */
	.htotal = OTM_HACT + OTM_HFP + OTM_HSW + OTM_HBP,
	/* height */
	.vdisplay = OTM_VACT,
	/* vsync_start = vdisplay + vfp */
	.vsync_start = OTM_VACT + OTM_VFP,
	/* vsync_end = vdisplay + vfp + vsw */
	.vsync_end = OTM_VACT + OTM_VFP + OTM_VSW,
	/* vtotal = vdisplay + vfp + vsw + vbp */
	.vtotal = OTM_VACT + OTM_VFP + OTM_VSW + OTM_VBP,
	.vrefresh = 60,
};

static const struct panel_otm1289a_desc otm1289a_desc = {
	.modes = &otm1289a_mode,
	.num_modes = 1,
	.bpc = 6,
	.size = {
		.width = 69,
		.height = 123,
	},
	.flag = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE,
	.lanes = 4,
	.format = MIPI_DSI_FMT_RGB888,
};

static const struct of_device_id panel_otm1289a_of_match[] = {
	{
		.compatible = "panel-otm1289a",
		.data = &otm1289a_desc,
	}, {
		/* sentinel */
	}
};
MODULE_DEVICE_TABLE(of, panel_otm1289a_of_match);

static int panel_otm1289a_pre_probe(struct mipi_dsi_device *dsi)
{
	const struct of_device_id *id;

	if (!of_device_is_available(dsi->dev.of_node))
		return -ENODEV;

	DRM_DEBUG_KMS("%s,%d\n", __FILE__, __LINE__);
	id = of_match_node(panel_otm1289a_of_match, dsi->dev.of_node);
	if (!id)
		return -ENODEV;

	return panel_otm1289a_probe(&dsi->dev, id->data);
}

static struct mipi_dsi_driver panel_otm1289a_dsi_driver = {
	.driver = {
		.name = "panel-otm1289a",
		.of_match_table = panel_otm1289a_of_match,
	},
	.probe = panel_otm1289a_pre_probe,
	.remove = panel_otm1289a_remove,
	.shutdown = panel_otm1289a_shutdown,
};

static int __init panel_otm1289a_init(void)
{
	int err;

	DRM_DEBUG_KMS("%s,%d\n", __FILE__, __LINE__);
	err = mipi_dsi_driver_register(&panel_otm1289a_dsi_driver);
	if (err < 0)
		return err;

	return 0;
}
module_init(panel_otm1289a_init);

static void __exit panel_otm1289a_exit(void)
{
	mipi_dsi_driver_unregister(&panel_otm1289a_dsi_driver);
}
module_exit(panel_otm1289a_exit);

MODULE_AUTHOR("lomboswer <lomboswer@lombotech.com>");
MODULE_DESCRIPTION("DRM Driver for Panels otm1289a");
MODULE_LICENSE("GPL and additional rights");
